﻿// -----------------------------------------------------------------------
// <copyright file="Descriptor.cs" company="none">
// Copyright (c) 2012 M. Alberti, xanatos(at)live.it
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// </copyright>
// -----------------------------------------------------------------------

namespace FluentSerializer
{
    using System;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using FluentSerializer.Utilities;

    /// <summary>
    /// 
    /// </summary>
    public class Descriptor
    {
        /// <summary>
        /// 
        /// </summary>
        public readonly Type Type;

        /// <summary>
        /// 
        /// </summary>
        public readonly bool IsUniversal;

        /// <summary>
        /// 
        /// </summary>
        public readonly LambdaExpression DeserializeExpression;

        /// <summary>
        /// 
        /// </summary>
        public readonly LambdaExpression SerializeExpression;

        /// <summary>
        /// 
        /// </summary>
        public readonly LambdaExpression SizeExpression;

        /// <summary>
        /// 
        /// </summary>
        public readonly Expression DefaultExpression;

        /// <summary>
        /// 
        /// </summary>
        public readonly LambdaExpression DisposeExpression;

        /// <summary>
        /// 
        /// </summary>
        public readonly DeserializationHandling DeserializationHandling;

        /// <summary>
        /// 
        /// </summary>
        public readonly Delegate Deserialize;

        /// <summary>
        /// 
        /// </summary>
        public readonly Delegate Serialize;

        /// <summary>
        /// 
        /// </summary>
        public readonly Delegate Size;

        /// <summary>
        /// 
        /// </summary>
        protected static readonly ConstructorInfo NotSupportedExceptionConstructor = typeof(NotSupportedException).GetConstructor(new[] { typeof(string) });

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <param name="isUniversal"></param>
        /// <param name="deserializeExpression"></param>
        /// <param name="deserializationHandling"></param>
        /// <param name="serializeExpression"></param>
        /// <param name="sizeExpression"></param>
        /// <param name="disposeExpression"></param>
        public Descriptor(Type type, bool isUniversal, LambdaExpression deserializeExpression, DeserializationHandling deserializationHandling, LambdaExpression serializeExpression, LambdaExpression sizeExpression, LambdaExpression disposeExpression = null)
        {
            if (deserializeExpression == null)
            {
                throw new ArgumentNullException("deserializeExpression");
            }

            if ((isUniversal && deserializeExpression.Parameters.Count != 2) || (!isUniversal && deserializeExpression.Parameters.Count != 3))
            {
                throw new ArgumentException("deserializeExpression");
            }

            if (deserializeExpression.Parameters[0].Type != typeof(BinaryReaderStream))
            {
                throw new ArgumentException("deserializeExpression");
            }

            if (serializeExpression == null)
            {
                throw new ArgumentNullException("serializeExpression");
            }

            if ((isUniversal && serializeExpression.Parameters.Count != 2) || (!isUniversal && serializeExpression.Parameters.Count != 3))
            {
                throw new ArgumentException("serializeExpression");
            }

            if (serializeExpression.Parameters[0].Type != typeof(BinaryWriterStream))
            {
                throw new ArgumentException("serializeExpression");
            }

            if (sizeExpression == null)
            {
                throw new ArgumentNullException("sizeExpression");
            }

            if ((isUniversal && sizeExpression.Parameters.Count != 1) || (!isUniversal && sizeExpression.Parameters.Count != 2))
            {
                throw new ArgumentException("sizeExpression");
            }

            if (disposeExpression != null)
            {
                if (disposeExpression.Parameters.Count != 1)
                {
                    throw new ArgumentException("disposeExpression");
                }
            }

            this.Type = type;
            this.IsUniversal = isUniversal;

            this.DeserializeExpression = deserializeExpression;
            this.SerializeExpression = serializeExpression;

            // We try to compress multiple sums
            var block = sizeExpression.Body as BlockExpression;

            if (block != null)
            {
                bool ok = block.Variables.Count == 1;

                Expression sum2 = ok ? block.Variables[0] : null;

                LabelExpression le;

                // Here we handle the sizeExpression generated by the IFluentSerializer<,>.GetSerializer() expansion.
                if (!ok && block.Variables.Count == 0 && block.Expressions.Count == 2 && (le = block.Expressions[1] as LabelExpression) != null)
                {
                    var block2 = block.Expressions[0] as BlockExpression;

                    GotoExpression ge;

                    if (block2 != null && block2.Variables.Count == 1 && block2.Expressions.Count == 2 && (ge = block2.Expressions[1] as GotoExpression) != null && ge.Target == le.Target && ge.Value == (sum2 = block2.Variables[0]))
                    {
                        var block3 = block2.Expressions[0] as BlockExpression;

                        if (block3 != null && block3.Variables.Count == 0)
                        {
                            block = block3;
                            ok = true;
                        }
                    }
                }

                if (ok)
                {
                    int sum = 0;

                    if (block.Expressions.All(p =>
                        {
                            if (p.NodeType != ExpressionType.Assign && p.NodeType != ExpressionType.AddAssign)
                            {
                                return false;
                            }

                            var be = (BinaryExpression)p;
                            ConstantExpression ce;

                            if (be.Left != sum2 || (ce = be.Right as ConstantExpression) == null)
                            {
                                return false;
                            }

                            if (p.NodeType == ExpressionType.Assign)
                            {
                                sum = (int)ce.Value;
                            }
                            else
                            {
                                sum += (int)ce.Value;
                            }

                            return true;
                        }))
                    {
                        sizeExpression = Expression.Lambda(Expression.Constant(sum), sizeExpression.Parameters);
                    }
                }
            }

            this.SizeExpression = sizeExpression;

            Type undecorated = Serializer.UndecorateType(type);

            if (undecorated.IsValueType)
            {
                deserializationHandling |= FluentSerializer.DeserializationHandling.RequireAssignment;
            }

            if (deserializationHandling.HasFlag(FluentSerializer.DeserializationHandling.RequireAssignment))
            {
                this.DefaultExpression = Expression.Default(undecorated);
            }
            else
            {
                if (undecorated.IsArray)
                {
                    this.DefaultExpression = Expression.NewArrayBounds(undecorated.GetElementType(), Serializer.Zero);
                }
                else
                {
                    ConstructorInfo constructor = undecorated.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, System.Type.EmptyTypes, null);

                    this.DefaultExpression = constructor != null ?
                        (Expression)Expression.New(constructor) :
                        Expression.Throw(Expression.New(NotSupportedExceptionConstructor, Expression.Constant(string.Format("Missing default constructor for {0}", undecorated.FullName))));
                }
            }

            if (disposeExpression != null)
            {
                deserializationHandling |= FluentSerializer.DeserializationHandling.RequireDispose;
                this.DisposeExpression = disposeExpression;
            }

            this.DeserializationHandling = deserializationHandling;

            this.Deserialize = deserializeExpression.Compile();
            this.Serialize = serializeExpression.Compile();
            this.Size = sizeExpression.Compile();
        }
    }
}